package smcrypto

import (
	"fmt"
	"github.com/ethereum/go-ethereum/sm2"
	"math/big"
)

const (
	pubkeyCompressed   byte = 0x2 // y_bit + x coord
	pubkeyUncompressed byte = 0x4 // x coord + y coord
	pubkeyHybrid       byte = 0x6 // y_bit + x coord + y coord
)

const (
	PubKeyBytesLenCompressed   = 33
	PubKeyBytesLenUncompressed = 65
	PubKeyBytesLenHybrid       = 65
)

// 加密
func Encrypt(pub *sm2.PublicKey, hash []byte) ([]byte, error) {
	data, err := sm2.Encrypt(pub, hash, sm2.C1C3C2)
	if err != nil {
		return nil, err
	}
	return data, nil
}

// IsCompressedPubKey returns true the the passed serialized public key has
// been encoded in compressed format, and false otherwise.
func IsCompressedPubKey(pubKey []byte) bool {
	// The public key is only compressed if it is the correct length and
	// the format (first byte) is one of the compressed pubkey values.
	return len(pubKey) == PubKeyBytesLenCompressed &&
		(pubKey[0]&^byte(0x1) == pubkeyCompressed)
}

// 验签
// pub *PublicKey, userId []byte, src []byte, sign []byte
func VerifySign(pub *sm2.PublicKey, sign, data []byte) bool {
	r, s, _, err := SignUnSerialize(sign)
	if err != nil {
		return false
	}

	return sm2.VerifyByRS(pub, sm2SignDefaultUserId, data, r, s)
}

func Verify(pub *sm2.PublicKey, sign *Signature, data []byte) bool {
	return sm2.VerifyByRS(pub, sm2SignDefaultUserId, data, sign.R, sign.S)
}

func UnCompressSerialize(pub *sm2.PublicKey) []byte {
	return pub.GetUnCompressBytes()
}

func GetRawBytes(pub *sm2.PublicKey) []byte {
	return pub.GetRawBytes()
}

func RawBytesToPublicKey(raw []byte) (*sm2.PublicKey, error) {
	pub, err := sm2.RawBytesToPublicKey(raw)
	if err != nil {
		return nil, err
	}
	return pub, nil
}

func SerializeUncompressed(p *sm2.PublicKey) []byte {
	b := make([]byte, 0, PubKeyBytesLenUncompressed)
	b = append(b, pubkeyUncompressed)
	b = paddedAppend(32, b, p.X.Bytes())
	return paddedAppend(32, b, p.Y.Bytes())
}

// SerializeCompressed serializes a public key in a 33-byte compressed format.
func SerializeCompressed(p *sm2.PublicKey) []byte {
	b := make([]byte, 0, PubKeyBytesLenCompressed)
	format := pubkeyCompressed
	if isOdd(p.Y) {
		format |= 0x1
	}
	b = append(b, format)
	return paddedAppend(32, b, p.X.Bytes())
}

// SerializeHybrid serializes a public key in a 65-byte hybrid format.
func SerializeHybrid(p *sm2.PublicKey) []byte {
	b := make([]byte, 0, PubKeyBytesLenHybrid)
	format := pubkeyHybrid
	if isOdd(p.Y) {
		format |= 0x1
	}
	b = append(b, format)
	b = paddedAppend(32, b, p.X.Bytes())
	return paddedAppend(32, b, p.Y.Bytes())
}

// IsEqual compares this PublicKey instance to the one passed, returning true if
// both PublicKeys are equivalent. A PublicKey is equivalent to another, if they
// both have the same X and Y coordinate.
func IsEqual(pub *sm2.PublicKey, otherPubKey *sm2.PublicKey) bool {
	return pub.X.Cmp(otherPubKey.X) == 0 && pub.Y.Cmp(otherPubKey.Y) == 0
}

// 33 65
func ParsePubKey(pubKey []byte) (*sm2.PublicKey, error) {
	if IsCompressedPubKey(pubKey) {
		return DecompressPublickey(pubKey)
	}
	return RawBytesToPublicKey(pubKey)
}

// Get last bit
func getLastBit(a *big.Int) uint {
	return a.Bit(0)
}

// 32byte
func zeroByteSlice() []byte {
	return []byte{
		0, 0, 0, 0,
		0, 0, 0, 0,
		0, 0, 0, 0,
		0, 0, 0, 0,
		0, 0, 0, 0,
		0, 0, 0, 0,
		0, 0, 0, 0,
		0, 0, 0, 0,
	}
}

// Compress publickey to 33  bytes.
func Compress(a *sm2.PublicKey) []byte {
	buf := []byte{}
	yp := getLastBit(a.Y)
	buf = append(buf, a.X.Bytes()...)
	if n := len(a.X.Bytes()); n < 32 {
		buf = append(zeroByteSlice()[:(32-n)], buf...)
	}
	// RFC: GB/T 32918.1-2016 4.2.9
	// if yp = 0, buf = 02||x
	// if yp = 0, buf = 03||x
	if yp == uint(0) {
		buf = append([]byte{byte(2)}, buf...)
	}
	if yp == uint(1) {
		buf = append([]byte{byte(3)}, buf...)
	}
	return buf
}

// DecompressPubkey parses a public key in the 33-byte compressed format.
func DecompressPublickey(pubkey []byte) (*sm2.PublicKey, error) {
	x, y := new(big.Int), new(big.Int)
	if len(pubkey) != 33 {
		return nil, fmt.Errorf("invalid public key")
	}
	if (pubkey[0] != 0x02) && (pubkey[0] != 0x03) {
		return nil, fmt.Errorf("invalid public key")
	}
	if x == nil {
		return nil, fmt.Errorf("invalid public key")
	}
	x.SetBytes(pubkey[1:])

	xxx := new(big.Int).Mul(x, x)
	xxx.Mul(xxx, x)

	ax := new(big.Int).Mul(big.NewInt(3), x)

	yy := new(big.Int).Sub(xxx, ax)
	//yy := new(big.Int).Add(xxx, ax)
	yy.Add(yy, sm2.GetSm2P256V1().Params().B)

	y1 := new(big.Int).ModSqrt(yy, sm2.GetSm2P256V1().Params().P)
	if y1 == nil {
		return nil, fmt.Errorf("can not revcovery public key")
	}

	y2 := new(big.Int).Neg(y1)
	y2.Mod(y2, sm2.GetSm2P256V1().Params().P)

	if pubkey[0] == 0x02 {
		if y1.Bit(0) == 0 {
			y = y1
		} else {
			y = y2
		}
	} else {
		if y1.Bit(0) == 1 {
			y = y1
		} else {
			y = y2
		}
	}
	//fmt.Println("dx:",x)
	//fmt.Println("dy:",y)
	return &sm2.PublicKey{X: x, Y: y, Curve: sm2.GetSm2P256V1()}, nil
}

// CompressPubkey encodes a public key to the 33-byte compressed format.
func CompressPublickey(pubkey *sm2.PublicKey) []byte {
	// big.Int.Bytes() will need padding in the case of leading zero bytes
	params := pubkey.Curve.Params()
	curveOrderByteSize := params.P.BitLen() / 8
	xBytes := pubkey.X.Bytes()
	signature := make([]byte, curveOrderByteSize+1)
	if pubkey.Y.Bit(0) == 1 {
		signature[0] = 0x03
	} else {
		signature[0] = 0x02
	}
	copy(signature[1+curveOrderByteSize-len(xBytes):], xBytes)
	return signature

}
